#include "blob.h"


blob::blob(int type, ofxVec2f pos, float mass, ofxVec2f velocity)
{

    image.loadImage("data/particle.png");
	this->type = type;
	this->mass = 1;
	this->targetMass = mass;
	this->initialMass = mass;
	this->velocity = velocity;

	if(type == 0)
	{
	    this->maxMass = 3000;
	    //this->targetMass = 2000;
	    this->initialMass = 800;
	}
	else this->maxMass = 6000 ;
	this->position = pos;
	dead = false;
	fadedOut = false;
	fadeStart = 0;
	friction = 0.992f;
	maxVelocity = 20;
	maxMaxVelocity = 90;
	exploding = false;
	readyToExplode = false;
	timeToExplode = 0;
	canExplode = type == 0;
    numFriends = 0;
    justExploded = false;
    targeted = 0;
    if(type == 1)
    {
        growSpeed = 200.0f;
        maxVelocity = 10.0f;
    }
	this->velocity = ofxVec2f(ofRandom(-1.0f,1.0f),ofRandom(-1.0f,1.0f)).normalized() * maxVelocity;

	seed1 = ofRandom(0.2f,1.3f);
	seed2 = ofRandom(0.2f,1.3f);
}


blob::~blob(void)
{
}


int blob::update()
{
	if(exploding)
	{
	    mass = fmax(1,mass-5000*testApp::deltaTime);

        bool done = true;
        for(int j=0; j<explosionData.size(); j++)
        {
            //Check if the distance is too big and we need to break
            ofxVec2f diff = explosionData[j].end->position-explosionData[j].start->position;
            explosionData[j].end->velocity +=-diff.normalized()*settings::EXPLOSION_ATTRACTION;
            if(diff.length() > getStringLength() || explosionData[j].end->exploding || explosionData[j].start->dead || explosionData[j].end->dead)
            {
                explosionData[j].broken = true;
                if(explosionData[j].fadeStart == 0) explosionData[j].fadeStart = ofGetElapsedTimeMillis();
            }

            //Make sure target explodes and stuff
            if(explosionData[j].value>1)
            {
                explosionData[j].end->readyToExplode = true;
                explosionData[j].broken = true;
                if(explosionData[j].fadeStart == 0) explosionData[j].fadeStart = ofGetElapsedTimeMillis();
            }
            else
               done = false;

            float bluh = settings::EXPLOSION_PARTICLE_MOVE / diff.length();

            if(explosionData[j].fadeStart == 0) explosionData[j].value +=testApp::deltaTime*bluh;

            if(explosionData[j].fadeStart > 0)
            {
                if(ofGetElapsedTimeMillis()-explosionData[j].fadeStart > settings::TIME_FADE_OUT) explosionData[j].fadedOut = true;
                else explosionData[j].fadeAlphaMod = (1-((float) (ofGetElapsedTimeMillis()-explosionData[j].fadeStart)/settings::TIME_FADE_OUT));
            }
        }

        if(done && !justExploded)
        {
            dead = true;
        }

        if(fadeStart != 0 && ofGetElapsedTimeMillis()-fadeStart > settings::TIME_FADE_OUT)
        {
            fadedOut = true;
        }

        //Clean up explosion data
        //Cleanup:
        vector<int> toDel;
        for(int i=explosionData.size()-1; i>=0; i--)
        {
            if(explosionData[i].broken && explosionData[i].fadedOut)
            {
                explosionData[i].end->targeted--;
                toDel.push_back(i);
            }

        }
        for(int i=0; i<toDel.size(); i++)
        {
            explosionData.erase(explosionData.begin()+toDel[i]);
        }
	}
	else
	{
            //velocity +=target*1.0f;

        if(velocity.length()>maxVelocity)
            velocity *= friction;

    if(velocity.length()>maxMaxVelocity)
        velocity = velocity.normalized()*maxMaxVelocity;


		//velocity *= friction;
		position += velocity*testApp::deltaTime;

		if(mass<=0)
			dead = true;


        if(mass<initialMass)
        {
            mass = mass+500*testApp::deltaTime;
        }
        else if(numFriends > 0 && type == 1)
        {
            growMass(testApp::deltaTime*growSpeed * log(numFriends*2.0f));
        }


        if(timeToExplode!=0 && timeToExplode<ofGetElapsedTimef())
            readyToExplode = true;

		if(position.x-getCoreRadius()<1)
		{
			position.x = 1 + getCoreRadius();
			velocity.x = -velocity.x;
		}
		if(position.x + getCoreRadius()>ofGetWidth()+1)
		{
			position.x = ofGetWidth()- 1 - getCoreRadius();
			velocity.x = -velocity.x;
		}
		if(position.y - getCoreRadius()<1)
		{
			position.y = 1 + getCoreRadius();
			velocity.y = -velocity.y;
		}
		if(position.y + getCoreRadius()>ofGetHeight()+1)
		{
			position.y = ofGetHeight()-1 - getCoreRadius();
			velocity.y = -velocity.y;
		}
	}
    numFriends = 0;

	return 1;
}

void blob::drawBehind()
{



}

void blob::drawLineTo(ofxVec2f pos)
{

    ofxVec2f diff = getPosition()-pos;

    //TODO: This should fade in/out based on distance
    if(diff.length()<settings::INTERACT_DISTANCE && !exploding)
    {
            ofxVec2f normal = diff.getPerpendicular().normalize();

            int numLines = 5;

            ofxVec2f myLineStep = (normal * getRadius() * 2) / numLines;
            ofxVec2f friendLineStep = (normal * 4 * 2) / numLines;

            ofNoFill();

            //ofSetColor(0,100,100,40);
            ofSetColor(150,100,0,40);

            for(int i=0; i<numLines; i++)
            {
                bool even = i % 2 == 0;

                int step = even ? i/2 : -i/2;

                ofSetLineWidth(2);

                ofxVec2f inBetween2 = (getPosition() - diff/2 + diff/15) * (1.0f + sinf(i*42.0f+ofGetElapsedTimef()*(18+i)) / 20);
                ofxVec2f inBetween1 = (getPosition() - diff/2 - diff/15) * (1.0f + sinf(i*2.0f+ofGetElapsedTimef()*(18+i)) / 20);

                ofBezier(getPosition().x + step * myLineStep.x, getPosition().y + step * myLineStep.y, inBetween1.x, inBetween1.y, inBetween2.x, inBetween2.y, pos.x + step * friendLineStep.x, pos.y + step * friendLineStep.y);
            }

            ofFill();
    }
}

void blob::drawImageAsCirlce(ofImage img, float x, float y, float radius, int count)
{

    //ofCircle(x,y,radius);
    int width = radius*2.0f;
    //ofSetColor(0,0,0,200);

    // image.draw(100,100,100,100);

    float baseDist = getRadius()/7;
    float speed = 6.0f*seed1;

    img.draw(x-width/2,y-width/2,width,width);
   /* if(count>1)
        for(int i = 0; i<count;i++)
        {
            float dist = baseDist+(float)i/(float)count;
            float size = width;
            img.draw(x-size/2+sinf(speed*ofGetElapsedTimef()*seed1+i)*dist,y-size/2+sinf(speed*ofGetElapsedTimef()*seed2+i)*dist,size,size);


        }*/
}

void blob::draw()
{
	if(dead && fadedOut)
		return;

    drawBehind();

    ofFill();

    float alphaMod = 1;

    if(fadeStart > 0)
    {
        alphaMod = (1-getFadedOutPercentage());
    }

    //explosion circle
    if(type == 1) ofSetColor(20,200,230,190*alphaMod);
    else ofSetColor(70,150,230,190*alphaMod);

    drawImageAsCirlce(image,getPosition().x, getPosition().y, getRadius(),1);

    //draw core
    if(type == 1) ofSetColor(200,40,50,255*alphaMod);
    else ofSetColor(0,80,150,255*alphaMod);

    //drawImageAsCirlce(image,getPosition().x, getPosition().y, getCoreRadius(),1);
    //drawImageAsCirlce(image,getPosition().x, getPosition().y, getCoreRadius(),1);
    //drawImageAsCirlce(image,getPosition().x, getPosition().y, getCoreRadius(),1);

    ofNoFill();

    ofSetLineWidth(6);


    if(type == 1)
    {
        //outer ring
        float bluh = 1 - (maxMass-mass)/maxMass;

        float alpha = 30 + 200 * bluh * alphaMod;

        ofSetColor(55 + 200 * bluh,100-100*bluh,100-100*bluh,alpha);

        ofCircle(getPosition().x,getPosition().y,sqrtf(maxMass/PI));
    }


    ofFill();

    for(int j=0; j<explosionData.size(); j++)
    {
        ofxVec2f diff = explosionData[j].end->position-explosionData[j].start->position;
        ofSetColor(230,100,50+diff.length()/getStringLength()*100,240*explosionData[j].fadeAlphaMod);

        float x = position.x+diff.x*explosionData[j].value;
        float y = position.y+diff.y*explosionData[j].value;

        drawImageAsCirlce(image,x,y,16,1);
        //drawImageAsCirlce(image,x,y,8,1);
        //drawImageAsCirlce(image,x,y,4,1);

        for(int i = 0; i < explosionData[j].betweenBlobParticles.size(); i++)
        {
            explosionData[j].betweenBlobParticles[i]->updateAndDraw(ofxVec2f(x,y), explosionData[j].fadedOut, explosionData[j].fadeAlphaMod);
        }
    }
}


float blob::getRadius()
{
	return min(getExplosionRadius(), sqrtf(mass/PI));

}

float blob::getInitialRadius()
{
	return min(getExplosionRadius(), sqrtf(initialMass/PI));

}

float blob::getCoreRadius()
{
    if(type == 0) return sqrtf(maxMass/PI) * 0.15f;
    else return sqrtf(maxMass/PI) * 0.30f;
}

//Returns the mass that was removed
float blob::setRadius(float radius)
{
	float oldMass = targetMass;

	targetMass = radius*radius*PI;

	return targetMass-oldMass;
}


float blob::growMass(float growth)
{
	mass +=growth;

    if(mass>maxMass)
    {
        mass = maxMass;
        readyToExplode = true;
    }

	return growth;
}

void blob::attract(ofxVec2f pos, float mass)
{
	ofxVec2f diff = position-pos;
	ofxVec2f change;

    change = diff.normalized()*mass;

    change = change*testApp::deltaTime;

	//velocity +=change;
}

void blob::interact(ofxVec2f pos)
{
	ofxVec2f diff =pos-position;
	ofxVec2f change = ofxVec2f(0,0);
    ofxVec2f diff2 = diff.normalized();
    float howMuch = 30000;
    if(type==1)
        howMuch = 12000;
   // diff2 *=100;
    if(diff.length()<settings::INTERACT_DISTANCE && diff.length()>10)
        change=(diff2)*testApp::deltaTime*howMuch/fmax(3.0f,powf(diff.length(),1.3f));

	velocity +=change;
}

void blob::testIfFriend(vector<blob*> friends)
{
    numFriends = 0;
    for(int j=0; j<friends.size(); j++)
    {
        if(friends[j] != this)
        {
            ofxVec2f diff = position-friends[j]->position;

            if(diff.length() < getStringLength())
            {
                if(diff.length() > getStringLength()/3*2)
                {
                    velocity +=-diff.normalized()*settings::FRIEND_ATTRACTION;
                }
                else if(diff.length() < getStringLength()/3)
                    velocity +=diff.normalized()*settings::FRIEND_ATTRACTION_LOW;

                numFriends++;

                if(justExploded && !friends[j]->exploding)
                {
                    explosionBetweenBlobs newE = explosionBetweenBlobs();
                    newE.start = this;
                    newE.end = friends[j];
                    newE.end->targeted++;
                    newE.value = 0;
                    newE.broken = false;
                    newE.fadeAlphaMod = 1.0f;
                    newE.fadedOut = false;
                    newE.fadeStart = 0;

                    for(int i = 0; i < 3; i++)
                    {
                         newE.betweenBlobParticles.push_back(new explosionParticle(position));
                    }

                    explosionData.push_back(newE);
                }
            }
        }
    }

    justExploded = false;
}

void blob::drawFriend(blob* friendBlob)
{

	ofxVec2f diff = position-friendBlob->position;

    ofxVec2f normal = diff.getPerpendicular().normalize();

    int numLines = min((int)(getStringPercent(diff) * settings::STRING_LINES), settings::MAX_STRING_LINES) + 1;

    ofxVec2f myLineStep = (normal * getRadius() * 2) / numLines;
    ofxVec2f friendLineStep = (normal * friendBlob->getRadius() * 2) / numLines;

	if(diff.length() < getStringLength())
	{
        ofNoFill();

        float alpha = 100 * getStringPercent(diff) + 20;

        // Adjust for fadeOut
        if(fadeStart > 0)
        {
            alpha *= (1 - getFadedOutPercentage());
        }

        ofSetColor(0,100,100,alpha);
        if(targeted>0)
            ofSetColor(150,100,50,alpha);

        for(int i=0; i<numLines; i++)
        {
            bool even = i % 2 == 0;

            int step = even ? i/2 : -i/2;

            ofSetLineWidth(3);

            ofxVec2f inBetween2 = (getPosition() - diff/2 + diff/15) * (1.0f + sinf(i*42.0f+ofGetElapsedTimef()*(18+i)) / 50);
            ofxVec2f inBetween1 = (getPosition() - diff/2 - diff/15) * (1.0f + sinf(i*2.0f+ofGetElapsedTimef()*(18+i)) / 50);

            ofBezier(getPosition().x + step * myLineStep.x, getPosition().y + step * myLineStep.y, inBetween1.x, inBetween1.y, inBetween2.x, inBetween2.y, friendBlob->getPosition().x + step * friendLineStep.x, friendBlob->getPosition().y + step * friendLineStep.y);
        }

        ofFill();
	}
}


void blob::onContact(blob * colPartner)
{
	float overlap = (colPartner->getPosition()-position).length()-getRadius()-colPartner->getRadius();

	if(overlap<0)
	{
		/*if(getRadius()>colPartner->getRadius())
			setRadius(getRadius()+abs(overlap));
		else
			setRadius(getRadius()-abs(overlap));
			*/
	}
}

float blob::getStringLength()
{
    return settings::STRING_RADIUS;
}

float blob::getStringPercent(ofxVec2f diff)
{
    return (getStringLength()-diff.length()) / getStringLength();
}

float blob::getFadedOutPercentage()
{
    return (float) (ofGetElapsedTimeMillis()-fadeStart)/settings::TIME_FADE_OUT;
}

vector<explosionParticle*> blob::explode()
{
	exploding = true;
	timeToDie = ofGetElapsedTimef()+settings::TIME_TO_LIVE_AFTER_EXPLOSION;
	vector<explosionParticle*> explosion;
	for(int i=0;i< 40;i++)
	{
		explosion.push_back(new explosionParticle(position));
	}
	justExploded = true;

    fadeStart = ofGetElapsedTimeMillis();

	return explosion;
}

float blob::getExplosionRadius()
{
	//Weird function that creates a radius that is not too small or too big
	return sqrtf(sqrtf(initialMass/PI))*20;
}

bool blob::shouldRemove()
{
    bool explosionFadedOut = true;

    for(int j=0; j<explosionData.size(); j++)
    {
        if(explosionFadedOut) explosionFadedOut = explosionData[j].fadedOut;

        if(!explosionFadedOut) break;
    }

    return dead && fadedOut && explosionFadedOut;
}
